Skip to main content

01 - 记忆与上下文的边界

前置:无。本篇是专题起点。

本篇回答:什么算记忆、什么不算,以及在什么情况下你需要这一层。

本篇会用到的词

意思
会话(session)一次连续的交互。会话内多轮共享同一个 messages 数组,会话结束这个数组通常被丢弃
语料库(corpus)RAG 检索的对象,事先准备好的一批文档。它跟具体某个用户无关
Profile(画像)一种记忆存储形态:每个用户一份结构化文档,新事实覆盖旧字段
Collection(集合)另一种形态:每条记忆是一条独立记录,只增不改,靠检索挑出相关的几条
检查点(checkpoint)持久化执行保存的执行状态快照。它记录"执行到哪一步",不记录"学到了什么"

一、没有记忆层时,具体是什么样

一个订餐助手,两次会话隔了六天:

# 第一次会话(周一)—— 用户主动说了一件重要的事
messages = [
{"role": "user", "content": "帮我订个餐,我对花生过敏"},
{"role": "assistant", "content": "好的,我会避开含花生的菜品……"},
]
# 会话结束,这个 list 被丢弃。它存在于进程内存或某张会话表里,
# 但下一次请求不会带上它 —— 带上就意味着上下文无限增长。

# 第二次会话(周日)—— 全新的 messages,模型这边一个字节的历史都没有
messages = [
{"role": "user", "content": "推荐个川菜馆"},
]
# 模型不是"忘了"花生过敏,是这次请求里根本没有这个信息。
# 它推荐宫保鸡丁没有任何异常 —— 在它收到的输入里,这是一个完全正常的推荐。

朴素的两种补法都不成立:

  • 把全部历史都带上。六天里可能有几十轮,token 成本线性增长,且真正相关的那一句被淹没在无关内容里 —— 这是上下文工程专题反复处理的问题
  • 让用户每次重说一遍。这等于把状态管理的责任推给用户,而"记住我说过的话"恰恰是助手类产品的核心体验

记忆层做的是第三件事:在第一次会话结束后,把"用户对花生过敏"这一条单独存下来;第二次会话开始时,只把这一条(以及其他几条相关的)放回上下文。

二、四个常被混作一谈的东西

判据只有两条:内容跟"这个用户"有没有关系,以及它活过一次请求之后还在不在上下文这次请求发出的 token活到:响应返回为止谁写:编排代码跟用户相关:是载体,不是存储记忆跨会话留下的事实活到:被作废或过期谁写:抽取或模型跟用户相关:是本专题的对象RAG事先备好的公共语料活到:语料被更新谁写:数据管道跟用户相关:否全体用户共享一份持久化执行执行到第几步的快照活到:任务结束谁写:运行时自动跟用户相关:否存进度,不存结论四者会同时出现在一次请求里:运行时从检查点恢复 → 记忆层取回三条用户事实 → RAG 取回两段文档 → 一起拼成上下文。
最常见的误用是把 RAG 需求接到记忆库上:文档跟具体用户无关,走记忆层会让每个用户各抽一遍事实,成本乘以用户数而召回反而更差。

2.1 记忆和 RAG 的判据:换个用户还成不成立

同一段文本,归属哪一层取决于它跟用户的关系:

内容归属理由
「退款政策是七天无理由」RAG对所有用户都一样,改一次全体生效
「这个用户上次退款被拒过」记忆换个用户就不成立
「产品手册第 3 章」RAG公共语料,用户没参与生产
「用户偏好深色模式」记忆由这个用户的交互产生

边界情况:企业知识库里"每个部门自己的规章"。它对个人不特定,但对群体特定。实践中按访问控制切分的语料走 RAG,把"这个用户属于哪个部门"作为一条记忆或直接作为请求参数 —— 不要为了少建一层,把整个部门知识库塞进记忆里。

2.2 记忆和持久化执行的判据:任务结束后还有没有用

持久化执行保存的是"这个长任务跑到第几步、中间变量是什么",任务一结束这份快照就没有价值了。记忆保存的是"从这个任务里学到的、下次还用得上的东西"。

两者在一个具体场景里同时出现:一个跑了四小时的数据清洗任务在第三小时崩了。

  • 检查点让它从第三小时接着跑,而不是从头开始 —— 任务完成后这份检查点可以删
  • 记忆记下的是"这个用户的 CSV 一律是 GBK 编码" —— 下一个任务还用得上

把后者写进检查点是常见错误:检查点按任务生命周期清理,这条经验会跟着任务一起被删掉。

三、三种记忆分型

分型不是学术分类,它决定了存储形态和更新策略。LangMem 的概念文档给出的三分法是目前引用最广的:

三种分型的差别不在"内容是什么",在"新的一条来了要不要覆盖旧的"语义记忆 · 事实是什么「用户对花生过敏」「团队用 Go」形态:Profile 画像 或 Collection 集合新旧冲突:需要消解,03 篇的主题绝大多数产品只做到这一层情景记忆 · 发生过什么「上次这样退款成功了,过程是……」形态:只能是 Collection,带时间戳新旧冲突:不消解,两次经历都成立当少样本示例用,也是审计线索程序记忆 · 该怎么做「回复这个客户时不要用表情」形态:直接改写系统提示词新旧冲突:后写的覆盖先写的改的是行为,回归风险最高
三者对"冲突"的处理完全相反:语义记忆必须消解冲突,情景记忆必须保留冲突(两次不同的经历都是真的),程序记忆则是直接覆盖。用同一套写入逻辑处理三者,是记忆层最常见的设计错误。

3.1 Profile 与 Collection:同一类事实的两种存法

语义记忆有两种存储形态,取舍很实在:

# 形态 A:Profile —— 每个用户一份结构化文档,字段固定
# 新事实进来是「改字段」,天然不会有两条矛盾的记录
profile = {
"user_id": "u_1024",
"dietary_restrictions": ["花生过敏"], # 新增一条过敏源就是往这个 list 里加
"preferred_cuisine": "川菜", # 口味变了就是覆盖这个字段
}
# 代价:字段是你事先定死的。用户说了一件你没设计过的事
#(「我周三晚上从不外食」),Profile 里没地方放,只能丢掉或塞进 notes 兜底字段。

# 形态 B:Collection —— 每条记忆一条记录,只增不改
collection = [
{"id": "m_01", "text": "用户对花生过敏", "created_at": "2026-08-18"},
{"id": "m_02", "text": "用户周三晚上从不外食", "created_at": "2026-08-19"},
{"id": "m_03", "text": "用户偏好川菜", "created_at": "2026-08-19"},
]
# 好处:任何事实都装得下,不需要事先设计 schema。
# 代价:① 读的时候要检索(Profile 是直接按 user_id 取整份)
# ② 会出现「用户偏好川菜」和「用户改吃粤菜了」两条并存 —— 冲突消解成为必需

实践中的分工:变化慢、字段可枚举的走 Profile,其余走 Collection。supermemory 这类产品把两者都做了,对外表现成"稳定事实 + 近期活动"两段拼接。

3.2 程序记忆为什么最危险

语义记忆写错了,最坏结果是模型多知道一条假事实,通常会被后续对话纠正。程序记忆写错了,改的是系统提示词 —— 它影响的是这个 Agent 之后所有会话的行为,而且没有任何一轮对话会去纠正它

一个真实形态的例子:用户抱怨"你回复太啰嗦了",程序记忆把"回复要简短"写进系统提示词。三周后,一个需要详细说明的场景下,Agent 给出了一句话的回答,没人知道这是三周前那条记忆在起作用。

这是为什么绝大多数生产系统只做语义记忆:程序记忆需要配套的版本管理和回归评测,成本高一个数量级

四、记忆层的四个动作

上半条是写入,下半条是读取,两条通路的延迟预算差两个数量级写入:可以慢(后台异步,秒级可接受)① 抽取对话 → 候选事实② 巩固跟旧记忆比对消解③ 落存储向量 / 图 / 文件遗忘(周期任务)过期、低命中、被作废读取:必须快(在用户等回复的这一轮里,毫秒级)④ 检索查询 → 候选记忆重排 + 截断留 3 到 10 条注入上下文位置影响前缀缓存模型调用这才是用户感知的那一次把①②放进读取那一轮("热路径写入")是一个真实选项,代价是每轮多一次模型调用的延迟 —— 02 篇第四节算这笔账。
四个动作对应本专题的 02 到 04 篇。图里唯一的虚线是遗忘 —— 它是最容易被省掉的一步,也是记忆库跑三个月后开始变慢变贵的原因。

五、什么情况下不该上记忆层

记忆层的引入成本是实打实的:一次额外的模型调用、一套额外的存储、一类新的线上故障(读回了错的记忆)。下面三种情况下这笔投入拿不到回报。

这三种情形下,记忆层增加的是故障面而不是能力任务本身就是一次性的翻译、摘要、单轮分类下一次请求跟这一次无关记下来的东西永远不会被读回纯成本事实在业务库里已有权威副本会员等级、收货地址、订阅状态直接查库,不要让模型抽取抽取会产生一份会过期的副本两边不一致时你信哪个要跨会话的只是执行进度长任务中断后续跑该用检查点,不是记忆检查点能精确恢复变量记忆只能给出一段近似描述
中间那一格是踩得最多的:把已经在数据库里的字段又抽取成一条记忆,之后用户在设置页改了地址,记忆里的旧地址还会被读回来 —— 而它看起来和真的一样。

判据可以压成一句话:这条信息在别处有没有权威副本?有 → 直接读那个副本。没有,且下次还用得上 → 才是记忆。

六、这一层的工程代价

上了记忆层之后新增的成本,按数量级列出来:

代价具体是什么量级
写入的模型调用每次会话结束跑一次抽取,输入是整段对话与对话本身的输入 token 同量级,即模型开销大致翻倍
读取的检索延迟向量检索 + 可选重排,在用户等待的那一轮里单纯向量检索几十毫秒;加交叉编码器重排会到数百毫秒
存储每条记忆一份文本 + 一个向量(1536 维 float32 约 6KB)每用户每月几百条的量级下,向量占用远大于文本
认知负担排查线上问题时多一个变量:"这次回答是不是被某条旧记忆带偏了"需要把读回的记忆条目打进 trace,见可观测性 01 篇
新的故障类型读回过期事实、读回别人的记忆、被投毒写入07 篇

"模型开销翻倍"这条经常被低估。 抽取的输入是整段对话,和业务调用同量级;如果再加上巩固阶段的第二次调用(把候选事实和已有记忆一起给模型判断),就是两倍到三倍。02 篇会讲怎么把它压下来 —— 主要手段是换小模型、批量处理、以及把巩固从"每次都问模型"改成"只在检索到相似条目时才问"。

七、小结

  • 判断一段信息归属哪一层,只需要两问:跟这个用户有没有关系(区分记忆与 RAG)、任务结束后还有没有用(区分记忆与检查点)
  • 三种记忆分型对冲突的处理方向完全相反,用一套逻辑处理三者会出错
  • 程序记忆改的是行为且没有自纠正机制,没有回归评测就不要上
  • 别处有权威副本的信息不要抽成记忆 —— 你会得到一份注定过期的影子数据

下一篇:02 - 写入路径:什么该被记下来,拆开抽取与巩固两个阶段,以及 mem0 从"让模型决定改哪条"退回"只追加"的那次转向。

← 回到 专题索引